<script setup lang="ts" name="ImageForm">
import { FileApi, FileImageType, FileImageUpdateType } from '@/api/infra/resource/library'
import Cropper from 'cropperjs'
import { dataURLtoBlob, urlToBase64 } from '@/utils/file/helper'
import { storeToRefs } from 'pinia'
import { useAppStoreWithOut } from '@/store/modules/app'
type CropendResult = {
  imgBase64: string;
  imgInfo: Cropper.Data;
}

type baseConfigType = {
  isCropper: boolean
  isKeepAspectRatio: boolean
  width: number
  height: number
  aspectRatio: number
}

const appStore = useAppStoreWithOut()
const { systemConfig } = storeToRefs(appStore)

const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗

const visible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用
const info = ref<FileImageType>()
const formData = ref<FileImageUpdateType>({
  id: undefined,
  url: '',
  name: '',
  alt: '',
  description: '',
  width: 0,
  height: 0
})
const formRules = reactive({
  name: [{ required: true, message: t('infra.resource.library.file.edit.image.rules', 'name'), trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const cropper = ref<Cropper>()
const currentOperate = ref('') // 当前操作
const scaleX = ref(1)
const scaleY = ref(1)
const isInit = ref(true)
const imageBase64 = ref('')
const baseConfig = ref<baseConfigType>({
  isCropper: false,
  isKeepAspectRatio: false,
  width: 1,
  height: 1,
  aspectRatio: NaN
})
// 图片裁剪相关参数
const options = ref<Cropper.Options>({
  viewMode: 1,
  dragMode: 'move', // 拖拽图片的模式 crop: 形成新的裁剪框|move:图片可移动|none: 什么也没有
  initialAspectRatio: NaN, // 定义裁剪框的初始宽高比。默认和图片容器的宽高比相同。 只有在 aspectRatio 的值为 NaN时才可以设置。
  aspectRatio: NaN, // 定义裁剪框的固定宽高比。默认是自由比例
  minContainerWidth: 200, // 设置裁剪容器的最小宽度
  minContainerHeight: 100, // 设置裁剪容器的最小高度
  minCanvasWidth: 0, // 设置图片容器的最小宽度
  minCanvasHeight: 0, // 设置图片容器的最小高度
  minCropBoxWidth: 0, // 设置裁剪框的最小宽度 相对于页面，而非图片
  minCropBoxHeight: 0 // 设置裁剪框的最小高度 相对于页面，而非图片
})

/** 打开弹窗 */
const open = async (id: number) => {
  visible.value = true
  resetForm()
  // 设置数据
  formLoading.value = true
  try {
    info.value = await FileApi.detail(id)
    if (!info.value) return
    baseConfig.value.width = info.value.width
    baseConfig.value.height = info.value.height
    formData.value.url = await urlToBase64(info.value.url)
    formData.value.name = info.value.name ? info.value.name : ''
    formData.value.alt = info.value.alt ? info.value.alt : ''
    formData.value.description = info.value.description ? info.value.description : ''
    formData.value.width = info.value.width
    formData.value.height = info.value.height
  } finally {
    formLoading.value = false
  }
}
defineExpose({ open }) // 提供 open 方法，用于打开弹窗

/** 提交表单 */
const emits = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调
const submitForm = async () => {
  // 校验表单
  if (!formRef) return
  const valid = await formRef.value.validate()
  if (!valid) return
  // 提交请求
  formLoading.value = true
  try {
    const file = dataURLtoBlob(imageBase64.value)
    const resBody = new FormData()
    if (!isInit.value) {
      resBody.append('file', file)
      if (formData.value.width) {
        resBody.append('width', formData.value.width.toString())
      }
      if (formData.value.height) {
        resBody.append('height', formData.value.height.toString())
      }
    } else {
      resBody.append('file', '')
    }
    resBody.append('name', formData.value.name as string)
    if (info.value && info.value.id) {
      resBody.append('id', info.value.id + '')
    }
    resBody.append('alt', formData.value.alt as string)
    resBody.append('description', formData.value.description as string)
    await FileApi.update(resBody)
    message.success(t('message', 'updateSuccess'))
    visible.value = false
    // 发送操作成功的事件
    emits('success', true)
  } finally {
    formLoading.value = false
  }
}

/** 重置表单 */
const resetForm = () => {
  formData.value ={
    id: undefined,
    url: '',
    name: '',
    alt: '',
    description: '',
    width: 0,
    height: 0
  }
  info.value = undefined
  isInit.value = true
  currentOperate.value = ''
  imageBase64.value = ''
  scaleX.value = 1
  scaleY.value = 1
  baseConfig.value = {
    isCropper: false,
    isKeepAspectRatio: false,
    width: 1,
    height: 1,
    aspectRatio: NaN
  }
  formRef.value?.resetFields()
}

// 宽度设置改变触发事件
const widthChangeHandler = (v) => {
  console.log(v)
  if (cropper.value) {
    const imgInfo = cropper.value.getData()
    if (!baseConfig.value.isCropper) {
      baseConfig.value.isCropper = true
      cropper.value.crop()
    }
    imgInfo.width = v
    if (baseConfig.value.isKeepAspectRatio) {
      imgInfo.height = v / baseConfig.value.aspectRatio
    } else {
      imgInfo.height = formData.value.height
    }
    cropper.value.setData(imgInfo)
  }
}

// 高度设置改变触发事件
const heightChangeHandler = (v) => {
  console.log(v)
  if (cropper.value) {
    const imgInfo = cropper.value.getData()
    if (!baseConfig.value.isCropper) {
      baseConfig.value.isCropper = true
      cropper.value.crop()
    }
    imgInfo.height = v
    if (baseConfig.value.isKeepAspectRatio) {
      imgInfo.width = v * baseConfig.value.aspectRatio
    } else {
      imgInfo.width = formData.value.width
    }
    cropper.value.setData(imgInfo)
  }
}

// 保持宽高比切换事件
const changeAspectRatioHandler = (v) => {
  if (cropper.value) {
    const cropBoxInfo = cropper.value.getCropBoxData()
    if (v) {
      const imgInfo = cropper.value.getData()
      if (Math.round(imgInfo.width) === 0) {
        cropper.value.setAspectRatio(info.value?.width! / info.value?.height!)
        baseConfig.value.aspectRatio = info.value?.width! / info.value?.height!
      } else {
        baseConfig.value.aspectRatio = Math.round(imgInfo.width) / Math.round(imgInfo.height)
        cropper.value.setAspectRatio(Math.round(imgInfo.width) / Math.round(imgInfo.height))
        cropper.value.setCropBoxData(cropBoxInfo)
      }
    } else {
      baseConfig.value.aspectRatio = NaN
      cropper.value.setAspectRatio(NaN)
      cropper.value.setCropBoxData(cropBoxInfo)
    }
  }
}

// 裁剪框发生变化触发事件
const handleCropend = ({ imgBase64, imgInfo }: CropendResult) => {
  if (!info.value) return
  imageBase64.value = imgBase64
  isInit.value = false
  if (baseConfig.value.isCropper) {
    formData.value.width = Math.round(imgInfo.width)
    formData.value.height = Math.round(imgInfo.height)
  } else {
    if (imgInfo.width === 0) {
      if (currentOperate.value === 'reset') {
        isInit.value = true
        formData.value.width = info.value.width
        formData.value.height = info.value.height
      } else {
        formData.value.width = baseConfig.value.width
        formData.value.height = baseConfig.value.height
      }
    } else {
      baseConfig.value.isCropper = true
      formData.value.width = Math.round(imgInfo.width)
      formData.value.height = Math.round(imgInfo.height)
      if (cropper.value) {
        cropper.value.crop()
      }
    }
  }
}

// 裁剪框确认事件
const saveCropper = () => {
  baseConfig.value.width = formData.value.width
  baseConfig.value.height = formData.value.height
  baseConfig.value.isCropper = false
  currentOperate.value = ''
  formData.value.url = imageBase64.value
  if (cropper.value) {
    cropper.value.replace(imageBase64.value)
    cropper.value.clear()
  }
}

// 裁剪实例完成触发事件
const handleReady = (cropperInstance: Cropper) => {
  cropper.value = cropperInstance
}

// 工具条点击事件
const handlerToolbar = (type: string, event: string, arg?: number) => {
  if (!info.value) return
  if (formData.value.url) {
    currentOperate.value = type
    if (event === 'scaleX') {
      scaleX.value = arg = scaleX.value === -1 ? 1 : -1
    }
    if (event === 'scaleY') {
      scaleY.value = arg = scaleY.value === -1 ? 1 : -1
    }
    if (event !== 'cropper') {
      cropper?.value?.[event]?.(arg)
      isInit.value = false
      if (event === 'reset') {
        isInit.value = true
        baseConfig.value.isCropper = false
        formData.value.width = info.value.width
        formData.value.height = info.value.height
        if (cropper.value) {
          cropper.value.replace(info.value.url)
          formData.value.url = info.value.url
          if (baseConfig.value.isKeepAspectRatio) {
            if (info.value.width && info.value.height) {
              cropper.value.setAspectRatio(info.value.width / info.value.height)
            }
          } else {
            cropper.value.setAspectRatio(NaN)
          }
          cropper.value.clear()
        }
      }
    } else {
      isInit.value = false
      baseConfig.value.isCropper = true
      if (cropper.value) {
        cropper.value.crop()
      }
    }
  }
}
</script>

<template>
  <Dialog v-model="visible" :title="t('infra.resource.library.file.edit.image', 'title')" width="1100px">
    <div v-loading="formLoading && systemConfig.loadingType === 'module'" class="cropper-wrap">
      <div class="cropper-left">
        <div class="cropper-content">
          <div :class="['cropper-image-wrap', baseConfig.isCropper ? 'cropper' : '']">
            <CropperImage v-if="info && formData.url" :src="formData.url" :alt="formData.alt" :mimeType="info.mimetype" height="330px" :circled="false" :options="options" @cropend="handleCropend" @ready="handleReady" />
            <div v-if="baseConfig.isCropper" class="image-size-text">
              <el-text>{{ t('infra.resource.library.file.edit.image', 'size') }}</el-text>
              <el-text>{{ formData.width }}</el-text>
              <el-text class="!mx-4px">x</el-text>
              <el-text>{{ formData.height }}</el-text>
            </div>
            <div v-if="baseConfig.isCropper" class="btn-save" @click="saveCropper">{{ t('action', 'confirm') }}</div>
          </div>
          <div class="cropper-toolbar">
            <div class="cropper-operate">
              <div :class="['item-operate', formData.url !== '' ? '' : 'disabled', currentOperate === 'reset' ? 'active' : '']" @click="handlerToolbar('reset', 'reset')">
                <div class="item-icon"><svg-icon icon="ad:reload-outlined" /></div>
                <div class="item-text">{{ t('action', 'reset') }}</div>
              </div>
              <div :class="['item-operate', formData.url !== '' ? '' : 'disabled', currentOperate === 'rotateLeft' ? 'active' : '']" @click="handlerToolbar('rotateLeft', 'rotate', -45)">
                <div class="item-icon"><svg-icon icon="ad:rotate-left-outlined" /></div>
                <div class="item-text">{{ t('cropper', 'rotateLeft') }}</div>
              </div>
              <div :class="['item-operate', formData.url !== '' ? '' : 'disabled', currentOperate === 'rotateRight' ? 'active' : '']" @click="handlerToolbar('rotateRight', 'rotate', 45)">
                <div class="item-icon"><svg-icon icon="ad:rotate-right-outlined" /></div>
                <div class="item-text">{{ t('cropper', 'rotateRight') }}</div>
              </div>
              <div :class="['item-operate', formData.url !== '' ? '' : 'disabled', currentOperate === 'horizontalFlip' ? 'active' : '']" @click="handlerToolbar('horizontalFlip', 'scaleX')">
                <div class="item-icon"><svg-icon icon="ci:horizontal-flip" /></div>
                <div class="item-text">{{ t('cropper', 'horizontalFlip') }}</div>
              </div>
              <div :class="['item-operate', formData.url !== '' ? '' : 'disabled', currentOperate === 'verticalFlip' ? 'active' : '']" @click="handlerToolbar('verticalFlip', 'scaleY')">
                <div class="item-icon"><svg-icon icon="ci:vertical-flip" /></div>
                <div class="item-text">{{ t('cropper', 'verticalFlip') }}</div>
              </div>
              <div :class="['item-operate', formData.url !== '' ? '' : 'disabled', currentOperate === 'zoomIn' ? 'active' : '']" @click="handlerToolbar('zoomIn', 'zoom', 0.1)">
                <div class="item-icon"><svg-icon icon="ad:zoom-in-outlined" /></div>
                <div class="item-text">{{ t('cropper', 'zoomIn') }}</div>
              </div>
              <div :class="['item-operate', formData.url !== '' ? '' : 'disabled', currentOperate === 'zoomOut' ? 'active' : '']" @click="handlerToolbar('zoomOut', 'zoom', -0.1)">
                <div class="item-icon"><svg-icon icon="ad:zoom-out-outlined" /></div>
                <div class="item-text">{{ t('cropper', 'zoomOut') }}</div>
              </div>
              <div :class="['item-operate', formData.url !== '' ? '' : 'disabled', currentOperate === 'cropper' ? 'active' : '', baseConfig.isCropper ? 'cropper' : '']" @click="handlerToolbar('cropper', 'cropper')">
                <div class="item-icon"><svg-icon icon="ri:crop-line" /></div>
                <div class="item-text">{{ t('cropper', 'title') }}</div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="cropper-right">
        <ElForm ref="formRef" label-width="auto" label-position="top" :model="formData" :rules="formRules">
          <ElFormItem :label="t('infra.resource.library.file.edit.image', 'name')" prop="name">
            <ElInput v-model="formData.name" clearable maxlength="50" show-word-limit placeholder="" />
          </ElFormItem>
          <ElFormItem :label="t('infra.resource.library.file.edit.image', 'alt')">
            <ElInput v-model="formData.alt" type="textarea" clearable maxlength="150" :autosize="false" resize="none" show-word-limit placeholder="" />
          </ElFormItem>
          <ElFormItem :label="t('infra.resource.library.file.edit.image', 'description')">
            <ElInput v-model="formData.description" type="textarea" clearable maxlength="150" :autosize="false" resize="none" show-word-limit placeholder="" />
          </ElFormItem>
          <div class="el-form-group">
            <ElFormItem :label="t('infra.resource.library.file.edit.image', 'width')">
              <ElInputNumber v-model="formData.width" :min="0" :max="info && info.width" @change="widthChangeHandler" />
            </ElFormItem>
            <ElFormItem class="label-text">
              <p>px</p>
            </ElFormItem>
            <ElFormItem :label="t('infra.resource.library.file.edit.image', 'height')">
              <ElInputNumber v-model="formData.height" type="number" :min="0" :max="info && info.height" @change="heightChangeHandler" />
            </ElFormItem>
            <ElFormItem class="label-text">
              <p>px</p>
            </ElFormItem>
            <ElFormItem class="label-empty">
              <ElCheckbox v-model="baseConfig.isKeepAspectRatio" @change="changeAspectRatioHandler">{{ t('infra.resource.library.file.edit.image', 'keepAspectRatio') }}</ElCheckbox>
            </ElFormItem>
          </div>
        </ElForm>
      </div>
    </div>
    <template #footer>
      <el-button @click="visible = false">{{ t('action', 'cancel') }}</el-button>
      <el-button :disabled="formLoading" type="primary" @click="submitForm">{{ t('action', 'confirm') }}</el-button>
    </template>
  </Dialog>
</template>

<style scoped lang="scss">
.cropper-wrap {
  display: flex;
  flex-direction: row;

  .cropper-left,
  .cropper-right {
    height: 400px;
  }

  .cropper-left {
    width: 50%;
  }

  .cropper-right {
    width: 50%;
    padding-left: var(--el-space);

    .el-form {
      overflow: hidden;

      .el-form-item {
        :deep(.el-form-item__content) {
          .el-textarea {
            .el-textarea__inner {
              height: 88px;
            }
          }
        }
      }
    }
  }

  .cropper-content {
    position: relative;

    .cropper-image-wrap {
      position: relative;
      height: 330px;
      background: #eee;

      &.cropper {
        background-image: linear-gradient(45deg,
            rgba(0, 0, 0, 0.25) 25%,
            transparent 0,
            transparent 75%,
            rgba(0, 0, 0, 0.25) 0),
          linear-gradient(45deg,
            rgba(0, 0, 0, 0.25) 25%,
            transparent 0,
            transparent 75%,
            rgba(0, 0, 0, 0.25) 0);
        background-position: 0 0, 12px 12px;
        background-size: 24px 24px;
      }

      .image-size-text {
        position: absolute;
        top: 10px;
        left: 10px;
        z-index: 10;
        height: 20px;
        padding: 0 5px;
        font-size: 12px;
        line-height: 20px;
        background: rgba(0, 0, 0, 0.5);
        border-radius: 2px;

        .el-text{
          color: var(--el-color-white);
        }
      }

      .btn-save {
        position: absolute;
        bottom: 10px;
        left: 50%;
        width: 98px;
        height: 26px;
        margin-left: -49px;
        font-size: 12px;
        line-height: 24px;
        color: var(--el-color-primary);
        text-align: center;
        cursor: pointer;
        background-color: #fff;
        border: 1px solid var(--el-color-primary);
        border-radius: 2px;
      }
    }
  }

  .cropper-toolbar {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: space-between;
    margin-top: 5px;

    .cropper-operate {
      display: flex;
      flex: 1;

      .item-operate {
        flex: 1;
        cursor: pointer;

        &:hover {
          .item-icon {
            background-color: var(--el-color-primary-light-9);
          }
        }

        &.disabled {
          cursor: not-allowed;
        }

        &.active {
          .item-icon {
            color: var(--el-color-primary);
            background-color: var(--el-color-primary-light-9);
          }

          .item-text {
            color: var(--el-color-primary);
          }
        }

        &.cropper {
          .item-icon {
            color: var(--el-color-primary);
          }
        }

        .item-icon {
          display: flex;
          align-items: center;
          justify-content: center;
          height: 32px;
          background-color: var(--el-fill-color-light);

          .el-icon {
            font-size: 20px !important;
          }
        }

        .item-text {
          display: flex;
          align-items: center;
          justify-content: center;
          height: 16px;
          margin-top: 2px;
          font-size: 12px;
        }
      }

      .item-operate+.item-operate {
        margin-left: 5px;
      }
    }
  }

  .cropper-preview {
    width: 224px;
    height: 224px;
    margin: 0 auto;
    overflow: hidden;
    border: 1px solid var(--el-border-color-lighter);

    img {
      width: 100%;
      height: 100%;
    }
  }

  .cropper-group {
    display: flex;
    padding-top: 20px;
    margin-top: 20px;
    border-top: 1px solid var(--el-border-color-lighter);
    align-items: center;
    justify-content: space-around;
  }
}

.el-form-group {
  display: flex;

  .el-form-item+.el-form-item {
    margin-top: 0;
  }

  .label-text {
    :deep(.el-form-item__content) {
      margin-top: 28px;

      p {
        margin: 0;
        margin-right: 10px;
      }
    }
  }

.label-empty {
  :deep(.el-form-item__content) {
    margin-top: 22px;
  }
}
}
</style>
